﻿@using System.Text
@using System.Globalization
@using MUnique.OpenMU.AdminPanel.Interop
@using MUnique.OpenMU.AdminPanel.Services
@using MUnique.OpenMU.DataModel.Configuration
@using MUnique.OpenMU.GameLogic
@using MUnique.OpenMU.Persistence
@using SixLabors.ImageSharp
@using SixLabors.ImageSharp.Formats.Png
@using SixLabors.ImageSharp.PixelFormats
@using System.ComponentModel

@implements IDisposable

<h3>@this.Map.Name</h3>

<div class="map-host" width="@(scale * 255)" height="@(scale * 255)">
    <img src="@(this.terrainImage.ToBase64String(PngFormat.Instance))"
         width="@(scale * 255)"
         height="@(scale * 255)" />
    @foreach (var enterGate in this.Map.EnterGates)
    {
        <div class="gate-enter"
             title="@enterGate.ToString()"
             style="@this.GetSizeAndPositionStyle(enterGate)"
             @onclick="@(_ => this.focusedObject = enterGate)">
            @if (this.focusedObject == enterGate)
            {
                <Resizers CoordinatesGetter="@this.OnGetObjectCoordinates" Resizing="@this.OnGateResizing" />
            }
        </div>
    }

    @foreach (var exitGate in this.Map.ExitGates)
    {
        <div class="gate-exit"
             title="@exitGate.ToString()"
             style="@this.GetSizeAndPositionStyle(exitGate)"
             @onclick="@(_ => this.focusedObject = exitGate)">
            @if (this.focusedObject == exitGate)
            {
                <Resizers CoordinatesGetter="@this.OnGetObjectCoordinates" Resizing="@this.OnGateResizing" />
            }
        </div>
    }

    @foreach (var spawn in this.Map.MonsterSpawns)
    {
        <div class="@this.GetSpawnClass(spawn)"
             title="@spawn.ToString()"
             style="@this.GetSizeAndPositionStyle(spawn)"
             @onclick="@(_ => this.focusedObject = spawn)">
            @if (this.focusedObject == spawn)
            {
                if (spawn.X1 != spawn.X2 || spawn.Y1 != spawn.Y2)
                {
                    <Resizers CoordinatesGetter="@this.OnGetObjectCoordinates" Resizing="@this.OnSpawnResizing" />
                }
                else if (spawn.Direction != Direction.Undefined)
                {
                    <div class="arrow @this.GetFocusedRotation()"></div>
                }
                else
                {
                    // show nothing
                }
            }
        </div>
    }
</div>

<div class="map-object-select">
    <label for="objectSelect">Selected Object:</label>
    <select title="Object" @onchange=this.OnObjectSelected id="objectSelect" autocomplete="off">
        <option value="@Guid.Empty" selected=@(this.focusedObject is null)></option>
        @foreach (var enterGate in this.Map.EnterGates)
        {
            <option value="@enterGate.GetId()" selected=@(object.Equals(this.focusedObject, enterGate))>EnterGate: @enterGate.ToString()</option>
        }
        @foreach (var exitGate in this.Map.ExitGates)
        {
            <option value="@exitGate.GetId()" selected=@(object.Equals(this.focusedObject, exitGate))>ExitGate: @exitGate.ToString()</option>
        }
        @foreach (var spawn in this.Map.MonsterSpawns)
        {
            <option value="@spawn.GetId()" selected=@(object.Equals(this.focusedObject, spawn))>Spawn: @spawn.ToString()</option>
        }
    </select>
</div>

@if (this.focusedObject != null)
{
    <div class="map-object-form">
        @if (focusedObject is EnterGate enterGate)
        {
            <AutoForm Model="@enterGate" OnValidSubmit="@this.OnValidSubmit"></AutoForm>
        }
        else if (focusedObject is ExitGate exitGate)
        {
            <AutoForm Model="@exitGate" OnValidSubmit="@this.OnValidSubmit"></AutoForm>
        }
        else if (focusedObject is MonsterSpawnArea spawn)
        {
            <AutoForm Model="@spawn" OnValidSubmit="@this.OnValidSubmit"></AutoForm>
        }
    </div>
}

@code {

    private Image<Rgba32> terrainImage = null!;

    private float scale = 3;

    private object? focusedObject;

    /// <summary>
    /// Gets or sets the map which is edited in this component.
    /// </summary>
    [Parameter]
    public GameMapDefinition Map { get; set; } = null!;

    /// <summary>
    /// Gets or sets the <see cref="EditForm.OnValidSubmit"/> event callback.
    /// </summary>
    [Parameter]
    public EventCallback OnValidSubmit { get; set; }

    /// <summary>
    /// Gets or sets the javascript runtime.
    /// </summary>
    /// <remarks>
    /// It's used for the resizing stuff. There is room for improvement, though.
    /// Currently, every mouse move in the resize process causes additional network roundtrips.
    /// This means, the resizing might be laggy and a bit buggy. It may make sense to do more stuff in javascript instead.
    /// </remarks>
    [Inject]
    public IJSRuntime JsRuntime { get; set; } = null!;

    /// <summary>
    /// Gets or sets the change notification service.
    /// </summary>
    [Inject]
    public IChangeNotificationService NotificationService { get; set; } = null!;

    /// <inheritdoc />
    public void Dispose()
    {
        this.NotificationService.PropertyChanged -= this.OnPropertyChanged;
    }

    /// <inheritdoc />
    protected override void OnInitialized()
    {
        base.OnInitialized();
        this.NotificationService.PropertyChanged += this.OnPropertyChanged;
    }

    /// <inheritdoc />
    protected override void OnParametersSet()
    {
        base.OnParametersSet();
        this.terrainImage = new GameMapTerrain(this.Map).ToImage();
    }

    private void OnPropertyChanged(object? sender, PropertyChangedEventArgs args)
    {
        if (sender == this.focusedObject)
        {
            this.StateHasChanged();
        }
    }

    private string GetSpawnClass(MonsterSpawnArea spawn)
    {
        var result = "spawn-single";
        if (spawn.X1 != spawn.X2 || spawn.Y1 != spawn.Y2)
        {
            result = "spawn-area";
        }

        if (this.focusedObject == spawn)
        {
            result += "-focused";
        }

        return result;
    }

    private string GetSizeAndPositionStyle(Gate gate)
    {
        return string.Format("width: {0}px; height: {1}px; top: {2}px; left:{3}px;",
            (scale * (1 + gate.Y2 - gate.Y1)).ToString(CultureInfo.InvariantCulture),
            (scale * (1 + gate.X2 - gate.X1)).ToString(CultureInfo.InvariantCulture),
            (scale * gate.X1).ToString(CultureInfo.InvariantCulture),
            (scale * gate.Y1).ToString(CultureInfo.InvariantCulture));
    }

    private string GetSizeAndPositionStyle(MonsterSpawnArea spawn)
    {
        var objScale = 1.0f;

        var result = new StringBuilder();

        if (spawn.X1 == spawn.X2 && spawn.Y1 == spawn.Y2)
        {
            // We want the small point to be more visible, so it's bigger and has a higher opacity.
            objScale = 1.75f;
        }

        // X and Y are twisted, it's not an error!
        var width = objScale * this.scale * (1 + spawn.Y2 - spawn.Y1);
        var height = objScale * this.scale * (1 + spawn.X2 - spawn.X1);
        var xyOffset = (objScale - 1.0f) * this.scale;
        var top = this.scale * spawn.X1 - xyOffset;
        var left = this.scale * spawn.Y1 - xyOffset;
        result.Append($"width: {width.ToString(CultureInfo.InvariantCulture)}px;")
            .Append($"height: {height.ToString(CultureInfo.InvariantCulture)}px;")
            .Append($"top: {top.ToString(CultureInfo.InvariantCulture)}px; left: {left.ToString(CultureInfo.InvariantCulture)}px;");

        return result.ToString();
    }

    private void OnObjectSelected(ChangeEventArgs args)
    {
        var obj = this.Map.EnterGates.FirstOrDefault<object>(g => g.GetId().ToString() == args.Value?.ToString())
                  ?? this.Map.ExitGates.FirstOrDefault<object>(g => g.GetId().ToString() == args.Value?.ToString())
                  ?? this.Map.MonsterSpawns.FirstOrDefault(g => g.GetId().ToString() == args.Value?.ToString());
        this.focusedObject = obj;
    }

    private async Task<(byte X, byte Y)?> OnGetObjectCoordinates(MouseEventArgs args)
    {
        if (args.Buttons == 1)
        {
            // For GetMapHostBoundingClientRect(), see map.js
            // Warning: it's NOT working with the Edge Browser! BoundingClientRect gets all values with 0.
            var mapClientRect = await this.JsRuntime.InvokeAsync<BoundingClientRect>("GetMapHostBoundingClientRect");
            var x = (args.ClientY - mapClientRect.Top) / scale;
            var y = (args.ClientX - mapClientRect.Left) / scale;
            return ((byte)x, (byte)y);
        }

        return null;
    }

    private void OnSpawnResizing((byte X, byte Y, Resizers.ResizerPosition resizerPosition) args)
    {
        if (this.focusedObject is MonsterSpawnArea focusedSpawnArea)
        {
            switch (args.resizerPosition)
            {
                case Resizers.ResizerPosition.TopLeft:
                    focusedSpawnArea.X1 = args.X;
                    focusedSpawnArea.Y1 = args.Y;
                    break;
                case Resizers.ResizerPosition.TopRight:
                    focusedSpawnArea.X1 = args.X;
                    focusedSpawnArea.Y2 = args.Y;
                    break;
                case Resizers.ResizerPosition.BottomRight:
                    focusedSpawnArea.X2 = args.X;
                    focusedSpawnArea.Y2 = args.Y;
                    break;
                case Resizers.ResizerPosition.BottomLeft:
                    focusedSpawnArea.X2 = args.X;
                    focusedSpawnArea.Y1 = args.Y;
                    break;
            }

            this.NotificationService.NotifyChange(focusedSpawnArea, null);
        }
    }

    private void OnGateResizing((byte X, byte Y, Resizers.ResizerPosition resizerPosition) args)
    {
        if (this.focusedObject is MonsterSpawnArea focusedGate)
        {
            switch (args.resizerPosition)
            {
                case Resizers.ResizerPosition.TopLeft:
                    focusedGate.X1 = args.X;
                    focusedGate.Y1 = args.Y;
                    break;
                case Resizers.ResizerPosition.TopRight:
                    focusedGate.X1 = args.X;
                    focusedGate.Y2 = args.Y;
                    break;
                case Resizers.ResizerPosition.BottomRight:
                    focusedGate.X2 = args.X;
                    focusedGate.Y2 = args.Y;
                    break;
                case Resizers.ResizerPosition.BottomLeft:
                    focusedGate.X2 = args.X;
                    focusedGate.Y1 = args.Y;
                    break;
            }

            this.NotificationService.NotifyChange(focusedGate, null);
        }
    }

    private string? GetFocusedRotation()
    {
        if (this.focusedObject is MonsterSpawnArea spawn)
        {
            return spawn.Direction.ToString().ToLowerInvariant();
        }

        return null;
    }
}
